﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Xml;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using Common.Utilities.TextMatch;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;

namespace Workflows.Components.TextMining.FullTextSearch
{
    /// <summary>
    /// 
    /// </summary>
    public class LuceneSearchWorker:IWorker 
    {
        #region Implementation of IWorker

        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        private Thread workerThread;
        private LuceneSearchInstruction instruction;
        private LuceneSearchOutput output;
        private RunContext _Context;
        private Guid _TicketID;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }

        /// <summary></summary>
        public Thread WorkerThread
        {
            get { return workerThread; }
        }
        /// <summary></summary>
        public InstructionBase Instruction
        {
            get { return instruction; }
        }
        /// <summary></summary>
        public ResultBase Output
        {
            get { return output; }
        }
        /// <summary></summary>
        public Type ComponentType
        {
            get { return typeof(LuceneSearchComponent); }
        }
        /// <summary></summary>
        public RunContext Context
        {
            get { return _Context; }
            set { _Context = value; }
        }
        /// <summary></summary>
        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this.instruction = (LuceneSearchInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }
        /// <summary></summary>
        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._TicketID = ticketGuid;
            this.instruction = (LuceneSearchInstruction)request;
            this._Context = context;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }
        /// <summary></summary>
        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
            }
            this.workerThread = Thread.CurrentThread;
            this.Search();
        }
        /// <summary></summary>
        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._TicketID, this, this.GetType(), this.instruction);
            }
            this.workerThread = new Thread(new ThreadStart(this.Search));
            this.workerThread.Start();
        }
        /// <summary></summary>
        public void CancelWork()
        {
            if (this.workerThread != null && this.workerThread.IsAlive)
            {
                this.workerThread.Abort();
                this.workerThread.Join();

                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._TicketID, this.GetType(), this.instruction);
                }
            }
        }

        #endregion

        #region work
        private void Search()
        {
            try
            {
                XmlDocument xDoc=new XmlDocument();
                xDoc.LoadXml("<SearchResults></SearchResults>");
                XmlNode root = xDoc.DocumentElement;

                Searcher luceneSearcher = new IndexSearcher(this.instruction.IndexingFilePath);
                StopWordsUtil stopWordUtil = new StopWordsUtil(StopWordsUtil.StopWordList.PubmedStopWordList);
                List<string> stopWords = stopWordUtil.StopWords;
                Analyzer analyzer = new StandardAnalyzer(stopWords.ToArray());
            	Dictionary<string, double> hitsFinalScores = new Dictionary<string, double>();
                Dictionary<string,string> hitsTitles=new Dictionary<string, string>();
                foreach(string fieldName in this.instruction.FieldWeights.Keys)
                {
					Dictionary<string, double> fieldHitsScores = new Dictionary<string, double>();
                    QueryParser parser = new QueryParser(fieldName, analyzer);
                    Query query = parser.Parse(this.instruction.Query);
                    Hits hits = luceneSearcher.Search(query);
                    if(hits.Length()==0)
                        continue;

                    string fieldLabel = StringUtil.RemoveNonAlphaNumericCharacters(fieldName);
                    XmlNode fieldSearchNode = XmlDataUtil.AddElement(ref xDoc, root, fieldLabel);
                    XmlDataUtil.UpdateAttribute(ref xDoc, fieldSearchNode,"FieldName",fieldName);
					XmlDataUtil.UpdateAttribute(ref xDoc, fieldSearchNode,"Hits",hits.Length().ToString());
                    XmlDataUtil.UpdateAttribute(
                        ref xDoc, fieldSearchNode, "Weight", this.instruction.FieldWeights[fieldName].ToString());
                    for(int i=0;i<hits.Length();i++)
                    {
                        Document luceneDoc = hits.Doc(i);
                        XmlNode hitNode = XmlDataUtil.AddElement(ref xDoc, fieldSearchNode, "Hit");
                    	string id = luceneDoc.Get(this.instruction.KeyField);
                        XmlDataUtil.UpdateAttribute(ref xDoc, hitNode,"Id",id);
                        string title = string.Empty;
                        if(!string.IsNullOrEmpty(this.instruction.TitleField))
                        {
                            title = luceneDoc.Get(this.instruction.TitleField);
                        }
                        XmlDataUtil.UpdateAttribute(ref xDoc, hitNode, "Title", title);
                    	double score = (double) hits.Score(i);
                        XmlDataUtil.UpdateAttribute(ref xDoc, hitNode,"Score",score.ToString());

						if(fieldHitsScores.ContainsKey(id))
						{
							if(fieldHitsScores[id]<score)
								fieldHitsScores[id] = score;
						}
						else 
							fieldHitsScores.Add(id,score);
                        if(!hitsTitles.ContainsKey(id))
                            hitsTitles.Add(id,title);
                    }

					foreach(string id in fieldHitsScores.Keys)
					{
						double weightedScore = this.instruction.FieldWeights[fieldName]*fieldHitsScores[id];
						if(hitsFinalScores.ContainsKey(id))
						{
							hitsFinalScores[id] = hitsFinalScores[id] + weightedScore;
						}
						else
						{
							hitsFinalScores.Add(id,weightedScore);
						}
					}
                }
				
				// weighted sum 
            	XmlNode summaryNode = XmlDataUtil.AddElement(ref xDoc, root, "Summary");
				foreach(string id in hitsFinalScores.Keys)
				{
					XmlNode hitNode = XmlDataUtil.AddElement(ref xDoc, summaryNode, "Hit");
					XmlDataUtil.UpdateAttribute(ref xDoc, hitNode, "Id", id);
				    string title = hitsTitles[id];
				    XmlDataUtil.UpdateAttribute(ref xDoc, hitNode, "Title", title);
					double score = hitsFinalScores[id];
					XmlDataUtil.UpdateAttribute(ref xDoc, hitNode, "Score", score.ToString());
				}

                xDoc.Save(this.instruction.SearchResultFilePath);

                this.output = new LuceneSearchOutput(
                    this.instruction.OwnerComponentID,
                    this._TicketID,
                    this.instruction.SearchResultFilePath);

                if (this.WorkerFinishedEvent != null)
                    this.WorkerFinishedEvent(this._TicketID, this.GetType(), this.instruction, this.output);
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                    this.WorkerErredEvent(this._TicketID, this.GetType(), this.instruction, ex.ToString(), true);
            }
        }
        #endregion
    }
}
